package com.pupil.down;

import com.pupil.down.system.SysSettings;
import com.pupil.down.thread.DownloadThread;
import com.pupil.down.thread.LogThread;
import com.pupil.down.util.FileUtil;
import com.pupil.down.util.HttpUtil;
import com.pupil.down.util.LogUtil;
import com.pupil.down.util.protocol.ProtocolFactory;
import com.pupil.down.util.protocol.ProtocolUtil;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

/**
 * @Description:
 * @Author: pupil
 * @Date: 2024/10/01 下午 8:14
 */
public class DownloadMain {

    // 下载线程池
    private static final ExecutorService executor = SysSettings.executor;

    public static void main(String[] args) throws Exception {
        // 设置是否开启日志的debug模式
        SysSettings.DEBUG = true;

        if (args == null || args.length == 0 || args[0].trim().length() == 0) {
            LogUtil.info("没有传入任何下载链接");
            LogUtil.info("支持 http/https/thunder 链接");
            return;
        }

        // 获取下载连接
        String url = args[0];
        // 判断是否支持此类型的连接
        boolean support = SysSettings.isSupport(url);
        if (!support) {
            LogUtil.info("不支持的协议类型");
            return;
        }

        LogUtil.info("要下载的链接是:{}", url);

        // 获取连接转换工具
        ProtocolUtil util = ProtocolFactory.getUtil(url);
        if (util != null) {
            url = util.toHttpUrl(url);
        }

        new DownloadMain().download(url);
    }


    /**
     * 执行下载
     *
     * @param url 下载连接
     * @throws Exception
     */
    public void download(String url) throws Exception {
        String fileName = HttpUtil.getHttpFileName(url);
        // 获取本地文件大小
        long localFileSize = FileUtil.getFileContentLength(SysSettings.PATH + url);
        // 获取网络文件大小
        Long httpFileContentLength = HttpUtil.getHttpFileContentLength(url);
        if (localFileSize >= httpFileContentLength) {
            LogUtil.info("{}已经下载完毕，无需重新下载", fileName);
            return;
        }

        List<Future<Boolean>> futureList = new ArrayList<>();
        if (localFileSize > 0) {
            LogUtil.info("开始断点续传 {}", fileName);
        } else {
            LogUtil.info("开始下载文件 {}", fileName);
        }

        LogUtil.info("开始下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        long startTime = System.currentTimeMillis();
        // 任务切分
        splitDownload(url, httpFileContentLength, futureList);
        // 日志线程
        LogThread logThread = new LogThread(httpFileContentLength);
        Future<Boolean> future = executor.submit(logThread);
        futureList.add(future);

        // 开始下载
        for (Future<Boolean> booleanFuture : futureList) {
            booleanFuture.get();
        }

        LogUtil.info("文件下载完毕 {}，本次下载耗时：{}", fileName, (System.currentTimeMillis() - startTime) / 1000 + "s");
        LogUtil.info("结束下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));

        // 文件合并
        boolean merge = merge(fileName);
        if (merge) {
            // 清理分段文件
            clearTemp(fileName);
        }

        LogUtil.info("本次文件下载结束");
        System.exit(0);
    }

    /**
     * 切分下载任务到多个线程
     *
     * @param url        下载连接
     * @param futureList future集合
     * @throws IOException
     */
    public void splitDownload(String url, Long httpFileContentLength, List<Future<Boolean>> futureList) throws IOException {
        int downloadThreadNum = SysSettings.DOWNLOAD_THREAD_NUM;
        // 任务切分
        long size = httpFileContentLength / downloadThreadNum;
        long lastSize = httpFileContentLength - (size * (downloadThreadNum - 1));

        for (int i = 0; i < downloadThreadNum; i++) {
            long start = i * size;
            long temp = (i == downloadThreadNum - 1) ? lastSize : size;
            long end = start + temp;

            start = start != 0 ? start + 1 : start;

            // 文件下载线程
            DownloadThread downloadThread = new DownloadThread(url, start, end, i, httpFileContentLength);
            // 提交线程
            Future<Boolean> future = executor.submit(downloadThread);
            futureList.add(future);
        }
    }

    /**
     * 合并文件
     * 将分片文件的内容写入最终文件中
     * @param fileName 文件名
     * @return
     */
    public boolean merge(String fileName) {
        LogUtil.info("开始合并文件 {}", fileName);
        fileName = SysSettings.PATH + fileName;
        String fileTempSuffix = SysSettings.FILE_TEMP_SUFFIX;
        byte[] buffer = new byte[1024 * 10];
        int len = -1;
        try (RandomAccessFile oSavedFile = new RandomAccessFile(fileName, "rw")) {
            int downloadThreadNum = SysSettings.DOWNLOAD_THREAD_NUM;
            for (int i = 0; i < downloadThreadNum; i++) {
                try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName + fileTempSuffix + i) {
                })) {
                    while ((len = bis.read(buffer)) != -1) { // 读到文件末尾则返回-1
                        oSavedFile.write(buffer, 0, len);
                    }
                }
            }
            LogUtil.info("文件合并完毕 {}", fileName);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 清理临时文件
     *
     * @param fileName 文件名
     * @return
     */
    public boolean clearTemp(String fileName) {
        String fileTempSuffix = SysSettings.FILE_TEMP_SUFFIX;
        int downloadThreadNum = SysSettings.DOWNLOAD_THREAD_NUM;
        String path = SysSettings.PATH;
        LogUtil.info("开始清理临时文件 {}{}0-{}", fileName, fileTempSuffix, downloadThreadNum - 1);
        for (int i = 0; i < downloadThreadNum; i++) {
            File file = new File(path + fileName + fileTempSuffix + i);
            file.delete();
        }
        LogUtil.info("临时文件清理完毕 {}{}0-{}", fileName, fileTempSuffix, downloadThreadNum - 1);

        return true;
    }

}
